package com.gf.framework.mongo.lambda;

import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.gf.framework.common.model.Page;
import com.gf.framework.mongo.entity.BaseEntity;
import com.gf.framework.mongo.service.GfMongoTemplate;
import com.mongodb.client.result.DeleteResult;

/**
 * @author gaofei
 * @date 2022/5/28 15:13
 */
import lombok.Data;
import org.bson.BsonRegularExpression;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Nullable;
import java.util.*;
import java.util.regex.Pattern;

public class MongoLambdaQuery<T extends BaseEntity> {
    private LambdaCriteria criteria;
    protected final GfMongoTemplate<T> mongoService;
    private MongoLambdaQuery(LambdaCriteria criteria, GfMongoTemplate<T> mongoService) {
        this.criteria = criteria;
        this.mongoService = mongoService;
    }

    public static <T extends BaseEntity> MongoLambdaQuery<T> where(GfMongoTemplate<T> mongoService, SFunction<T, Object> column) {
        return where(mongoService, getFieldName(column));
    }

    public static <T extends BaseEntity> MongoLambdaQuery<T> where(GfMongoTemplate<T> mongoService, String column) {
        return new MongoLambdaQuery<>(new LambdaCriteria(column), mongoService);
    }

    public static <T extends BaseEntity> MongoLambdaQuery<T> where(GfMongoTemplate<T> mongoService, Criteria criteria) {
        return new MongoLambdaQuery<>(new LambdaCriteria(criteria), mongoService);
    }

    /**
     * 等于
     * @param value
     * @return
     */
    public MongoLambdaQuery<T> is(Object value) {
         criteria.is(value);
         return this;
    }

    /**
     * 不等于
     * @param value
     * @return
     */
    public MongoLambdaQuery<T> ne(Object value) {
        criteria.ne(value);
        return this;
    }

    /**
     * 小于
     * @param value
     * @return
     */
    public MongoLambdaQuery<T> lt(Object value) {
        criteria.lt(value);
        return this;
    }

    /**
     * 小于等于
     * @param value
     * @return
     */
    public MongoLambdaQuery<T> lte(Object value) {
        criteria.lte(value);
        return this;
    }

    /**
     * 大于
     * @param value
     * @return
     */
    public MongoLambdaQuery<T> gt(Object value) {
        criteria.gt(value);
        return this;
    }

    /**
     * 大于等于
     * @param value
     * @return
     */
    public MongoLambdaQuery<T> gte(Object value) {
        criteria.gte(value);
        return this;
    }

    public MongoLambdaQuery<T> in(Collection<?> values) {
        criteria.in(values);
        return this;
    }

    public MongoLambdaQuery<T> in(Object... values) {
        criteria.in(values);
        return this;
    }

    public MongoLambdaQuery<T> nin(Collection<?> values) {
        criteria.nin(values);
        return this;
    }

    public MongoLambdaQuery<T> nin(Object... values) {
        criteria.nin(values);
        return this;
    }

    public MongoLambdaQuery<T> and(SFunction<T, Object> column) {
        return and(getFieldName(column));
    }

    public MongoLambdaQuery<T> and(String column) {
        criteria.and(column);
        return this;
    }

    public MongoLambdaQuery<T> and(Criteria criteria) {
        this.criteria = this.criteria.and(criteria);
        return this;
    }

    public MongoLambdaQuery<T> and(MongoLambdaQuery<T> lambdaWrapper) {
        return and(lambdaWrapper.getCriteria());
    }

    public MongoLambdaQuery<T> or(Criteria criteria) {
        this.criteria = this.criteria.or(criteria);
        return this;
    }

    public MongoLambdaQuery<T> or(MongoLambdaQuery<T> lambdaWrapper) {
        return or(lambdaWrapper.getCriteria());
    }

    public MongoLambdaQuery<T> nor(Criteria criteria) {
        this.criteria = this.criteria.nor(criteria);
        return this;
    }

    public MongoLambdaQuery<T> not() {
        criteria.not();
        return this;
    }

    public MongoLambdaQuery<T> regex(String regex) {
        criteria.regex(regex);
        return this;
    }

    public MongoLambdaQuery<T> regex(String regex, @Nullable String options) {
        criteria.regex(regex, options);
        return this;
    }

    public MongoLambdaQuery<T> regex(Pattern pattern) {
        criteria.regex(pattern);
        return this;
    }

    public MongoLambdaQuery<T> regex(BsonRegularExpression regex) {
        criteria.regex(regex);
        return this;
    }

    public MongoLambdaQuery<T> nor(MongoLambdaQuery<T> lambdaWrapper) {
        return nor(lambdaWrapper.getCriteria());
    }

    public MongoLambdaQuery<T> all(Object... values) {
        criteria.all(values);
        return this;
    }

    public MongoLambdaQuery<T> all(Collection<?> values) {
        criteria.all(values);
        return this;
    }

    public MongoLambdaQuery<T> alike(Example<?> example) {
        criteria.alike(example);
        return this;
    }

    public MongoLambdaQuery<T> size(int size) {
        criteria.size(size);
        return this;
    }

    public MongoLambdaQuery<T> exists(boolean value) {
        criteria.exists(value);
        return this;
    }

    public static <T> String getFieldName(SFunction<T, Object> column) {
        return MongoLambdaColumn.create().builder(column).getFieldName();
    }

    public Criteria getCriteria() {
        return criteria.getCriteria();
    }

    private Query getQueryWithNotPage() {
        return criteria.getQueryWithNotPage();
    }

    public Query getQuery() {
        return criteria.getQuery();
    }

    public T findOne() {
        return mongoService.findOne(getQuery());
    }

    public List<T> find() {
        return mongoService.find(getQuery());
    }

    public MongoLambdaQuery<T> page(int page, int size) {
        criteria.page(page, size);
        return this;
    }

    public MongoLambdaQuery<T> page(PageRequest pageRequest) {
        criteria.page(pageRequest);
        return this;
    }

    /**
     * 升序
     * @param column
     */
    public MongoLambdaQuery<T> asc(SFunction<T, Object> column) {
        asc(getFieldName(column));
        return this;
    }

    public MongoLambdaQuery<T> asc(String column) {
        criteria.asc(column);
        return this;
    }

    /**
     * 降序
     * @param column
     */
    public MongoLambdaQuery<T> desc(SFunction<T, Object> column) {
        desc(getFieldName(column));
        return this;
    }

    public MongoLambdaQuery<T> desc(String column) {
        criteria.desc(column);
        return this;
    }

    public MongoLambdaQuery<T> limit(int limit) {
        criteria.limit(limit);
        return this;
    }

    public long count() {
        return mongoService.count(getQuery());
    }

    public MongoLambdaQuery<T> skip(int skip) {
        criteria.skip(skip);
        return this;
    }

    public Page<T> findPage() {
        Query query = getQueryWithNotPage();
        long count = mongoService.count(query);
        List<T> data = find();
        Page<T> page = Page.of(
                criteria.getPageRequest().getPageNumber(),
                criteria.getPageRequest().getPageSize(), count);
        page.setRecords(data);
        return page;
    }

    public MongoLambdaUpdate<T> lambdaUpdate() {
        return new MongoLambdaUpdate<>(this);
    }

    public DeleteResult remove(){
        return mongoService.remove(getQuery());
    }

    public T findAndRemove() {
        return mongoService.findAndRemove(getQuery());
    }

    public List<T> findAllAndRemove() {
        return mongoService.findAllAndRemove(getQuery());
    }

    @Data
    public static class AAA {
        private String name;
    }

    public static void main(String[] args) {
        System.out.println(MongoLambdaColumn.create().builder(AAA::getName).getFieldName()); // name
    }
}
